/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.*;
import java.util.function.Supplier;

class GaussianBlur {
    public static final double BLUR_NUM_0_5 = 0.5;
    public static final int BLUR_NUM_1000 = 1000000;
    public static final int BLUR_NUM_120 = 120;
    public static final double BLUR_NUM_0_726 = 0.726;
    public static final int BLUR_NUM_NEGATIVE_2 = -2;
    public static final int BLUR_NUM_2 = 2;
    public static final int BLUR_NUM_8 = 8;
    public static final int BLUR_NUM_16 = 16;
    public static final int BLUR_NUM_24 = 24;
    public static final int BLUR_NUM_6 = 6;
    public static final int BLUR_NUM_4 = 4;
    public static final int BLUR_NUM_5 = 5;
    public static final int BLUR_NUM_3 = 3;
    public static final int BLUR_NUM_7 = 7;
    public static final int BLUR_NUM_12 = 12;
    public static final int BLUR_NUM_19 = 19;
    public static final int BLUR_NUM_9 = 9;
    public static final int HEX_FF = 0xff;
    public static final int HEX_7ED55D16 = 0x7ed55d16;
    public static final int HEX_C761C23C = 0xc761c23c;
    public static final int HEX_FFFFFFFF = 0xffffffff;
    public static final int HEX_FFFFFFF = 0xfffffff;
    public static final int HEX_165667B1 = 0x165667b1;
    public static final int HEX_D3A2646C = 0xd3a2646c;
    public static final int HEX_FD7046C5 = 0xfd7046c5;
    public static final int HEX_B55A4F09 = 0xb55a4f09;
    public static final int HEX_10000000 = 0x10000000;
    public static final int BLUR_ONE_HUNDRED_TWENTY = 20;
	
	// Configuration.
    public static double a0 = 0;
    public static double a1 = 0;
    public static double a2 = 0;
    public static double a3 = 0;
    public static double b1 = 0;
    public static double b2 = 0;
    public static double leftCorner = 0;
    public static double lrightCorner = 0;

    public static double[] gaussCoef(double sigma) {
        // debugLog("gaussCoef=====sigma: "+ sigma);
        if (sigma < BLUR_NUM_0_5) {
            sigma = BLUR_NUM_0_5;
        }

        double a = Math.exp(BLUR_NUM_0_726 * BLUR_NUM_0_726) / sigma;
        double g1 = Math.exp(-a);
        double g2 = Math.exp(BLUR_NUM_NEGATIVE_2 * a);
        double k = (1 - g1) * (1 - g1) / (1 + BLUR_NUM_2 * a * g1 - g2);
        double a0 = k;
        double a1 = k * (a - 1) * g1;
        double a2 = k * (a + 1) * g1;
        double a3 = -k * g2;
        double b1 = BLUR_NUM_2 * g1;
        double b2 = -g2;
        double leftCorner = (a0 + a1) / (1 - b1 - b2);
        double lrightCorner = (a2 + a3) / (1 - b1 - b2);
        // debugLog("gaussCoef=====([a0, a1, a2, a3, b1, b2, leftCorner, lrightCorner]): " + a0 + ", " + a1 + ", " + a2 + ", " + a3 + ", " + b1 + ", " + b2 + ", " + leftCorner + ", " + lrightCorner);
        return new double[] { a0, a1, a2, a3, b1, b2, leftCorner, lrightCorner };
    }

    public static void convolveRGBA(int[] src, int[] out, double[] line, double[] coeff, int width, int height) {
        // takes src image and writes the blurred and transposed result into out
        // debugLog("convolveRGBA=====src length: " + src.length);
        // debugLog("convolveRGBA=====out length: " + out.length);
        // debugLog("convolveRGBA=====line length: " + line.length);
        // debugLog("convolveRGBA=====coeff: " + coeff.length);
        // debugLog("convolveRGBA=====width: " + width);
        // debugLog("convolveRGBA=====height: " + height);
        int rgb;
        double preSrcR;
        double preCrcG;
        double preSrcB;
        double preSrcA;
        double currSrcR;
        double currSrcG;
        double currSrcB;
        double currSrcA;
        double currOutR;
        double currOutG;
        double currOutB;
        double currOutA;
        double preOutR;
        double preOutG;
        double preOutB;
        double preOutA;
        double prepreOutR;
        double prepreOutG;
        double prepreOutB;
        double prepreOutA;
        int srcIndex;
        int outIndex;
        int lineIndex;
        double coeffA0;
        double coeffA1;
        double coeffB1;
        double coeffB2;
        int i;
        int j;
        for (i = 0; i < height; i++) {
            srcIndex = i * width;
            outIndex = i;
            lineIndex = 0;

            // left to right
            rgb = src[srcIndex];
			
            preSrcR = rgb & HEX_FF;
            preCrcG = (rgb >> BLUR_NUM_8) & HEX_FF;
            preSrcB = (rgb >> BLUR_NUM_16) & HEX_FF;
            preSrcA = (rgb >> BLUR_NUM_24) & HEX_FF;

            prepreOutR = preSrcR * coeff[BLUR_NUM_6];
            prepreOutG = preCrcG * coeff[BLUR_NUM_6];
            prepreOutB = preSrcB * coeff[BLUR_NUM_6];
            prepreOutA = preSrcA * coeff[BLUR_NUM_6];

            preOutR = prepreOutR;
            preOutG = prepreOutG;
            preOutB = prepreOutB;
            preOutA = prepreOutA;

            coeffA0 = coeff[0];
            coeffA1 = coeff[1];
            coeffB1 = coeff[BLUR_NUM_4];
            coeffB2 = coeff[BLUR_NUM_5];
			
            for (j = 0; j < width; j++) {
                rgb = src[srcIndex];
                currSrcR = rgb & HEX_FF;
                currSrcG = (rgb >> BLUR_NUM_8) & HEX_FF;
                currSrcB = (rgb >> BLUR_NUM_16) & HEX_FF;
                currSrcA = (rgb >> BLUR_NUM_24) & HEX_FF;

                currOutR = currSrcR * coeffA0 + preSrcR * coeffA1 + preOutR * coeffB1 + prepreOutR * coeffB2;
                currOutG = currSrcG * coeffA0 + preCrcG * coeffA1 + preOutG * coeffB1 + prepreOutG * coeffB2;
                currOutB = currSrcB * coeffA0 + preSrcB * coeffA1 + preOutB * coeffB1 + prepreOutB * coeffB2;
                currOutA = currSrcA * coeffA0 + preSrcA * coeffA1 + preOutA * coeffB1 + prepreOutA * coeffB2;

                prepreOutR = preOutR;
                prepreOutG = preOutG;
                prepreOutB = preOutB;
                prepreOutA = preOutA;

                preOutR = currOutR;
                preOutG = currOutG;
                preOutB = currOutB;
                preOutA = currOutA;

                preSrcR = currSrcR;
                preCrcG = currSrcG;
                preSrcB = currSrcB;
                preSrcA = currSrcA;

                line[lineIndex] = preOutR;
                line[lineIndex + 1] = preOutG;
                line[lineIndex + BLUR_NUM_2] = preOutB;
                line[lineIndex + BLUR_NUM_3] = preOutA;
                lineIndex += BLUR_NUM_4;
                srcIndex++;
            }
            srcIndex--;
            lineIndex -= BLUR_NUM_4;
            outIndex += height * (width - 1);

            // right to left
            rgb = src[srcIndex];
			
            preSrcR = rgb & HEX_FF;
            preCrcG = (rgb >> BLUR_NUM_8) & HEX_FF;
            preSrcB = (rgb >> BLUR_NUM_16) & HEX_FF;
            preSrcA = (rgb >> BLUR_NUM_24) & HEX_FF;

            prepreOutR = preSrcR * coeff[BLUR_NUM_7];
            prepreOutG = preCrcG * coeff[BLUR_NUM_7];
            prepreOutB = preSrcB * coeff[BLUR_NUM_7];
            prepreOutA = preSrcA * coeff[BLUR_NUM_7];

            preOutR = prepreOutR;
            preOutG = prepreOutG;
            preOutB = prepreOutB;
            preOutA = prepreOutA;

            currSrcR = preSrcR;
            currSrcG = preCrcG;
            currSrcB = preSrcB;
            currSrcA = preSrcA;

            coeffA0 = coeff[BLUR_NUM_2];
            coeffA1 = coeff[BLUR_NUM_3];
            
            for (j = width - 1; j >= 0; j--) {
                currOutR = currSrcR * coeffA0 + preSrcR * coeffA1 + preOutR * coeffB1 + prepreOutR * coeffB2;
                currOutG = currSrcG * coeffA0 + preCrcG * coeffA1 + preOutG * coeffB1 + prepreOutG * coeffB2;
                currOutB = currSrcB * coeffA0 + preSrcB * coeffA1 + preOutB * coeffB1 + prepreOutB * coeffB2;
                currOutA = currSrcA * coeffA0 + preSrcA * coeffA1 + preOutA * coeffB1 + prepreOutA * coeffB2;

                prepreOutR = preOutR;
                prepreOutG = preOutG;
                prepreOutB = preOutB;
                prepreOutA = preOutA;

                preOutR = currOutR;
                preOutG = currOutG;
                preOutB = currOutB;
                preOutA = currOutA;

                preSrcR = currSrcR;
                preCrcG = currSrcG;
                preSrcB = currSrcB;
                preSrcA = currSrcA;

                rgb = src[srcIndex];
                currSrcR = rgb & HEX_FF;
                currSrcG = (rgb >> BLUR_NUM_8) & HEX_FF;
                currSrcB = (rgb >> BLUR_NUM_16) & HEX_FF;
                currSrcA = (rgb >> BLUR_NUM_24) & HEX_FF;
				
                rgb =
                    ((int)(line[lineIndex] + preOutR) << 0) +
                    ((int)(line[lineIndex + 1] + preOutG) << BLUR_NUM_8) +
                    ((int)(line[lineIndex + BLUR_NUM_2] + preOutB) << BLUR_NUM_16) +
                    ((int)(line[lineIndex + BLUR_NUM_3] + preOutA) << BLUR_NUM_24);
					
                out[outIndex] = rgb;
				
                srcIndex--;
                lineIndex -= BLUR_NUM_4;
                outIndex -= height;
            }
        }
        // debugLog("out length: " + out.length);
    }

    public static void blurRGBA(int[] src, int width, int height, double radius) {
        // debugLog("convolveRGBA=====src length: " + src.length);
        // debugLog("convolveRGBA=====width: " + width);
        // debugLog("convolveRGBA=====height: " + height);
        // debugLog("convolveRGBA=====radius: " + radius);
        // Quick exit on zero radius
        if (radius == 0) {
            return;
        }

        // Unify input data type, to keep convolver calls isomorphic
        final int[] src32 = Arrays.copyOf(src, src.length);
        final int[] out = new int[src32.length];
        final double[] tmpLine = new double[Math.max(width, height) * BLUR_NUM_4];
        final double[] coeff = gaussCoef(radius);

        convolveRGBA(src32, out, tmpLine, coeff, width, height);
        convolveRGBA(out, src32, tmpLine, coeff, height, width);
    }
    
    public static void debugLog(String msg) {
        boolean debug = false;
        if (debug) {
            System.out.println(msg);
        }
    }
}

/*
 *  @State
 *  @Tags Jetstream2
 */
class Benchmark {
    public static int width;
    public static int height;
    public static double radius;
    public static int[] buffer;
    /*
     * @Setup
     */
    Benchmark() {
        this.width = 800;
        this.height = 450;
        this.radius = 15;
        final Supplier<Integer> rand = () -> {
            int seedNum = 49734321;
            seedNum = (seedNum + GaussianBlur.HEX_7ED55D16 + (seedNum << GaussianBlur.BLUR_NUM_12)) & GaussianBlur.HEX_FFFFFFFF;
            seedNum = (seedNum ^ GaussianBlur.HEX_C761C23C ^ (seedNum >>> GaussianBlur.BLUR_NUM_19)) & GaussianBlur.HEX_FFFFFFFF;
            seedNum = (seedNum + GaussianBlur.HEX_165667B1 + (seedNum << GaussianBlur.BLUR_NUM_5)) & GaussianBlur.HEX_FFFFFFFF;
            seedNum = ((seedNum + GaussianBlur.HEX_D3A2646C) ^ (seedNum << GaussianBlur.BLUR_NUM_9)) & GaussianBlur.HEX_FFFFFFFF;
            seedNum = (seedNum + GaussianBlur.HEX_FD7046C5 + (seedNum << GaussianBlur.BLUR_NUM_3)) & GaussianBlur.HEX_FFFFFFFF;
            seedNum = (seedNum ^ GaussianBlur.HEX_B55A4F09 ^ (seedNum >>> GaussianBlur.BLUR_NUM_16)) & GaussianBlur.HEX_FFFFFFFF;
            return (int)((seedNum & GaussianBlur.HEX_FFFFFFF) / GaussianBlur.HEX_10000000);
        };
        final int[] bufferArry = new int[width * height];
        for (int i = 0; i < bufferArry.length; i++) {
            bufferArry[i] = rand.get();
        }
        this.buffer = bufferArry;
    }
    /*
     * @Benchmark
     */
    public void runIteration() {
        long startTime = System.nanoTime();
        for (int i = 0; i < GaussianBlur.BLUR_ONE_HUNDRED_TWENTY; i++) {
            GaussianBlur.blurRGBA(buffer, width, height, radius);
        }
        long endTime = System.nanoTime();
        System.out.println("gaussian-blur: ms = " + (double)(endTime - startTime)/GaussianBlur.BLUR_NUM_1000);
    }
    public static void main(String[] args) {
        new Benchmark().runIteration();
    }    
}
